﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.Utils;

namespace Xceed.Silverlight.DataGrid
{
  public partial class DataGridControl
  {
    #region ILayoutElement Life cycle

    internal ILayoutElement CreateAndPrepareElement( DataPath itemPath )
    {
      if( itemPath.LastChild is GroupDataItem )
      {
        return this.CreateAndPrepareGroup( itemPath );
      }
      else
      {
        return this.CreateAndPrepareRow( itemPath );
      }
    }

    internal RowWrapper CreateAndPrepareRow( DataPath itemPath )
    {
      Row row = this.CreateRow( itemPath.LastChild.Data );
      if( row is DataRow )
      {
        this.PrepareElementInternal( ( DataRow )row, itemPath );
      }
      else
      {
        row.CellsOffset = this.GetColumnElementsIdentForDepth(itemPath.Depth);
      }
      return new RowWrapper( row );
    }

    internal virtual VirtualGroupContainer CreateAndPrepareGroup( DataPath itemPath )
    {
      List<DataGridGroupConfiguration> groupConfigList = this.GetGroupConfigurationForPath( itemPath );
      Debug.Assert( groupConfigList.Count != 0 );

      VirtualGroupContainer group = null;
      if( group == null )
      {
        group = this.CreateVirtualGroupContainer( itemPath );
      }

      this.PrepareElementInternal( group, itemPath, groupConfigList );

      return group;
    }

    internal void AttachElementInternalCore( ILayoutElement element, DataPath path )
    {
      RowWrapper wrapper = element as RowWrapper;
      if( wrapper != null )
      {
        DataRow dataRow = wrapper.Row as DataRow;
        if( dataRow != null )
        {
          this.AttachElementInternal( dataRow, path );
        }
      }
      else
      {
        this.AttachElementInternal( ( VirtualGroupContainer )element, path );
      }
    }

    #endregion

    #region IDataGridElement Life cycle

    internal void AttachElementInternal( IDataGridElement element, DataPath dataPath )
    {
      element.SetDataPath( dataPath );
    }

    internal void DetachElementInternal( IDataGridElement element )
    {
    }

    internal void ClearElementInternalCore( IDataGridElement element )
    {
      if( element is DataRow )
        this.ClearElementInternal( ( DataRow )element );
      else
        Debug.Assert( false, "Unexpected element type" );
    }

    internal void DetachElementInternalCore( IDataGridElement element )
    {
      if( element is DataRow )
        this.DetachElementInternal( ( DataRow )element );
      else
        Debug.Assert( false, "Unexpected element type" );
    }

    #endregion

    #region ColumnContainer Life cycle

    internal ColumnContainer GetContainerForColumn( Column column, ColumnPosition position, bool updateSizeOrPositionOnly )
    {
      ColumnPanel columnPanel = this.DataGridPanel.ColumnPanel;
      ColumnContainer columnContainer;
      if( !columnPanel.TryGetElement( column, out columnContainer )
        && !columnPanel.TryGetRecycledElement( column, this.CurrentColumn, out columnContainer ) )
      {
        columnContainer = this.CreateAndPrepareElement( column, position );
      }

      if( !object.ReferenceEquals( columnContainer.Column, column ) )
      {
        this.DetachElementInternal( columnContainer );
      }

      this.AttachElementInternal( columnContainer, column, position, updateSizeOrPositionOnly );

      return columnContainer;
    }

    internal ColumnContainer CreateAndPrepareElement( Column column, ColumnPosition position )
    {
      ColumnContainer container = new ColumnContainer();
      this.PrepareElementInternal( container, column, position );
      return container;
    }

    internal void PrepareElementInternal( ColumnContainer columnContainer, Column column, ColumnPosition position )
    {
      columnContainer.Column = column;
      columnContainer.ColumnPosition = position;
      foreach( IColumnElementHost columnHost in this.DataGridPanel.ColumnElementHosts )
      {
        this.CreateAndPrepareElement( columnHost, columnContainer );
      }
    }

    internal void ClearElementInternal( ColumnContainer columnContainer )
    {
      foreach( ColumnElementWrapper element in columnContainer.Wrappers.ToList() )
      {
        this.ClearColumnElementWrapper( element );
      }
    }

    internal void AttachElementInternal( ColumnContainer columnContainer, Column column, ColumnPosition position, bool updateSizeOnly )
    {
      bool columnChanged = !object.Equals( columnContainer.Column, column );

      if( columnChanged )
      {
        columnContainer.Column = column;
        updateSizeOnly = false;
      }

      columnContainer.ColumnWidth = column.ActualWidth;
      columnContainer.ColumnPosition = position;

      if( !updateSizeOnly )
      {
        foreach( IColumnElement element in columnContainer.Elements )
        {
          this.AttachElementInternalCore( element, column, columnChanged );
        }
      }
    }

    internal void DetachElementInternal( ColumnContainer columnContainer )
    {
      foreach( IColumnElement element in columnContainer.Elements )
      {
        this.DetachElementInternalCore( element );
      }
    }

    #endregion

    #region IColumnElementHost Life cycle

    internal int PrepareElementInternal( IColumnElementHost columnElementHost )
    {
      int createdElement = 0;

      IDictionary<Column, ColumnElementWrapper> existingColumsDict = columnElementHost.Elements.ToDictionary( ( e ) => e.Column );

      foreach( ColumnContainer columnContainer in this.DataGridPanel.AllColumns )
      {
        if( existingColumsDict.ContainsKey( columnContainer.Column ) )
          continue;

        ColumnElementWrapper colWrapper = this.CreateAndPrepareElement( columnElementHost, columnContainer );

        this.AttachElementInternalCore( colWrapper.ColumnElement, colWrapper.ColumnElement.ParentColumnContainer.Column, true );
        createdElement++;
      }

      return createdElement;
    }

    internal void ClearElementInternal( IColumnElementHost columnElementHost )
    {
      foreach( ColumnElementWrapper colWrapper in columnElementHost.Elements.ToList() )
      {
        this.ClearColumnElementWrapper( colWrapper );
      }
    }

    #endregion

    #region IColumnElement Life cycle

    internal ColumnElementWrapper CreateAndPrepareElement( IColumnElementHost columnElementHost, ColumnContainer columnContainer )
    {
      Column column = columnContainer.Column;
      ColumnElementWrapper wrapper = this.CreateElementCore( columnElementHost, column );

      this.PrepareColumnElementWrapper( wrapper, columnElementHost, columnContainer );

      return wrapper;
    }

    internal void PrepareColumnElementWrapper( ColumnElementWrapper columnWrapper, IColumnElementHost columnElementHost, ColumnContainer columnContainer )
    {
      columnWrapper.ColumnElement.SetParentColumnContainer( columnContainer );
      columnWrapper.ColumnElement.SetParentHost( columnElementHost );
      columnElementHost.AddColumnElement( columnWrapper );
      columnContainer.Add( columnWrapper );

      this.PrepareElementInternalCore( columnWrapper, columnContainer.Column );
    }

    internal void ClearColumnElementWrapper( ColumnElementWrapper columnWrapper )
    {
      IColumnElement element = columnWrapper.ColumnElement;
      IColumnElementHost columnElementHost = element.ParentHost;
      ColumnContainer columnContainer = element.ParentColumnContainer;

      this.ClearElementInternalCore( element );

      columnElementHost.RemoveColumnElement( columnWrapper );
      columnContainer.Remove( columnWrapper );
      columnWrapper.ColumnElement.SetParentColumnContainer( null );
      columnWrapper.ColumnElement.SetParentHost( null );
    }

    internal ColumnElementWrapper CreateElementCore( IColumnElementHost columnElementHost, Column column )
    {
      ColumnElementWrapper wrapper;

      if( columnElementHost is DataRow )
        wrapper = this.CreateDataCellInternal( ( DataRow )columnElementHost, column );
      else if( columnElementHost is ColumnManagerRow )
        wrapper = this.CreateColumnManagerCellInternal( ( ColumnManagerRow )columnElementHost, column );
      else if( columnElementHost is RootHost )
        wrapper = this.CreateColumnBackgroundControlInternal( column );
      else if( columnElementHost is Row )
        wrapper = this.CreateCellInternal( ( Row )columnElementHost, column );
      else
        throw new InvalidOperationException( "Column type is not recognize." );

      return wrapper;
    }

    internal void PrepareElementInternalCore( ColumnElementWrapper wrapper, Column column )
    {
      IColumnElement element = wrapper.ColumnElement;

      if( element is DataCell )
        this.PrepareElementInternal( ( DataCell )element, column );
      else if( element is ColumnManagerCell )
        this.PrepareElementInternal( ( ColumnManagerCell )element, column );
      else if( element is ColumnBackgroundControl )
        this.PrepareElementInternal( ( ColumnBackgroundControlWrapper )wrapper, column );
      else if( element is Cell )
        this.PrepareElementInternal( ( Cell )element, column );
      else
        Debug.Assert( false, "Unexpected element type" );
    }

    internal void ClearElementInternalCore( IColumnElement element )
    {
      if( element is DataCell )
        this.ClearElementInternal( ( DataCell )element );
      else if( element is ColumnManagerCell )
        this.ClearElementInternal( ( ColumnManagerCell )element );
      else if( element is ColumnBackgroundControl )
        this.ClearElementInternal( ( ColumnBackgroundControl )element );
      else if( element is Cell )
        this.ClearElementInternal( ( Cell )element );
      else
        Debug.Assert( false, "Unexpected element type" );
    }

    internal void AttachElementInternalCore( IColumnElement element, Column column, bool columnChanged )
    {
      if( element is DataCell )
        this.AttachElementInternal( ( DataCell )element, column, columnChanged );
      else if( element is ColumnManagerCell )
        this.AttachElementInternal( ( ColumnManagerCell )element, column, columnChanged );
      else if( element is ColumnBackgroundControl )
        this.AttachElementInternal( ( ColumnBackgroundControl )element, column, columnChanged );
      else if( element is Cell )
        this.AttachElementInternalForCell( ( Cell )element, column, columnChanged );
      else
        Debug.Assert( false, "Unexpected element type" );
    }

    internal void DetachElementInternalCore( IColumnElement element )
    {
      if( element is DataCell )
        this.DetachElementInternal( ( DataCell )element );
      else if( element is ColumnManagerCell )
        this.DetachElementInternal( ( ColumnManagerCell )element );
      else if( element is ColumnBackgroundControl )
        this.DetachElementInternal( ( ColumnBackgroundControl )element );
      else if( element is Cell )
        this.DetachElementInternal( ( Cell )element );
      else
        Debug.Assert( false, "Unexpected element type" );
    }

    #endregion

    #region DataRow Life cycle

    protected virtual Row CreateRow( object userObject )
    {
      return new DataRow();
    }

    internal void PrepareElementInternal( DataRow row, DataPath dataPath )
    {
      row.CellsOffset = this.GetColumnElementsIdentForDepth( dataPath.Depth );
      this.PrepareElement( row, new DataGridPath( dataPath ) );
      // Optimization.
      // Do not call the PrepareElementInternal(IColumnElementHost) version 
      // to avoid creation of the cells. This will happen later on and
      // let the layout pass be quicker when needed. (i.e., user interaction)
    }

    internal void ClearElementInternal( DataRow row )
    {
      this.ClearElementInternal( ( IColumnElementHost )row );
      this.ClearElement( row );
    }

    internal void AttachElementInternal( DataRow row, DataPath dataPath )
    {
      this.AttachElementInternal( ( IDataGridElement )row, dataPath );
      this.UpdateElementInternal( row, false );

      row.Height = this.RowHeight;
      row.SetBackground( this.Background );
      row.SetAlternatingBackground( this.AlternatingRowBackground );
      row.SetRowStyle( this.RowStyle );
      this.UpdateRowCurrentState( row, null, false );

      this.AttachElement( row, new DataGridPath( dataPath ) );
    }

    internal void DetachElementInternal( DataRow row )
    {
      this.DetachElement( row );
      this.ResetElementInternal( row );

      this.DetachElementInternal( ( IDataGridElement )row );
    }

    internal bool UpdateElementInternal( DataRow row, bool initializeDisplay )
    {
      bool retval = false;

      if( initializeDisplay )
      {
        retval = this.InitializeDisplay( row );
      }

      if( ( row.IsDisplayInitialized ) && ( this.UpdateElementInternal( ( EditableRow )row ) ) )
      {
        retval = true;
      }

      return retval;
    }

    internal bool InitializeCells( DataRow row )
    {
      // No columns so no need to initialize cells.
      if( !this.DataGridPanel.AllColumns.Any() )
        return false;

      int createdCells = this.PrepareElementInternal( ( EditableRow )row );
      return ( createdCells > 0 );
    }

    protected virtual void PrepareElement( DataRow row, DataGridPath dataGridPath )
    {

    }

    protected virtual void ClearElement( DataRow row )
    {

    }

    protected virtual void AttachElement( DataRow row, DataGridPath dataGridPath )
    {

    }

    protected virtual void DetachElement( DataRow row )
    {

    }

    private bool InitializeDisplay( DataRow row )
    {
      DataPath dataPath = row.Path;
      if( row.IsDisplayInitialized || ( dataPath.IsStubPath() ) )
        return false;

      row.IsDisplayInitialized = true;
      row.IsSelected = this.IsPathSelected( row.Path );
      row.SetCommonState( this.ResolveRowCommonState( row.IsSelected, false, row.IsAlternate ), false );

      foreach( ColumnElementWrapper w in ( ( IColumnElementHost )row ).Elements )
      {
        DataCellWrapper dataCellWrapper = ( DataCellWrapper )w;
        dataCellWrapper.NotifyRowDisplayInitialized();

        if( !dataCellWrapper.DataCell.ParentColumnContainer.IsRecycled )
        {
          this.InitializeDisplay( dataCellWrapper.DataCell );
        }
      }

      return true;
    }

    private void UninitializeDisplay( DataRow row )
    {
      if( !row.IsDisplayInitialized )
        return;

      row.IsDisplayInitialized = false;
      foreach( ColumnElementWrapper w in row.Elements )
      {
        DataCellWrapper dataCellWrapper = ( DataCellWrapper )w;

        if( !dataCellWrapper.DataCell.ParentColumnContainer.IsRecycled )
        {
          this.UninitializeDisplay( dataCellWrapper.DataCell );
        }

        dataCellWrapper.NotifyRowDisplayInitialized();
      }

      row.IsSelected = false;
    }

    private void ResetElementInternal( DataRow row )
    {
      this.UninitializeDisplay( row );
      this.ResetElementInternal( ( EditableRow )row );
    }

    #endregion

    #region EditableRow Life cycle

    private bool UpdateElementInternal( EditableRow row )
    {
      EditableRowViewModel viewModel = this.GetEditableRowViewModel( row.Path );
      bool updated = ( row.ViewModel != viewModel );

      row.ViewModel = viewModel;

      foreach( var cell in row.RenderedCells )
      {
        EditableCell editableCell = cell as EditableCell;
        if( ( editableCell != null ) && this.UpdateElementInternal( editableCell, false ) )
        {
          updated = true;
        }
      }

      return updated;
    }

    private void ResetElementInternal( EditableRow row )
    {
      IEnumerable<EditableCell> editablesCells = ( from c in row.RenderedCells
                                                   let ec = c as EditableCell
                                                   where ( ec != null )
                                                   select ec ).ToList();
      var viewModel = row.ViewModel;
      bool releaseCellViewModel = ( viewModel == null );

      foreach( var cell in editablesCells )
      {
        this.ResetElementInternal( cell, releaseCellViewModel );
      }

      row.ViewModel = null;

      if( viewModel != null )
      {
        viewModel.Release();
      }
    }

    private EditableRowViewModel GetEditableRowViewModel( DataPath dataPath )
    {
      if( ( dataPath == null ) || ( dataPath.IsStubPath() ) )
        return null;

      var dataItem = dataPath.LastChild.Data;
      return m_dataGridViewModel.GetRow( dataPath, dataItem, false );
    }

    #endregion

    #region VirtualGroupContainer Life cycle

    internal VirtualGroupContainer CreateVirtualGroupContainer( DataPath groupPath )
    {
      VirtualGroupContainer group = new VirtualGroupContainer();
      GroupDataItem groupDataItem = ( GroupDataItem )groupPath.LastChild;

      List<DataGridGroupConfiguration> configs = this.GetGroupConfigurationForPath( groupPath );
      group.SetHeadersFooters(
        this.GetFixedHeaders( groupPath, configs ),
        this.GetFixedFooters( groupPath, configs ) );

      return group;
    }

    internal void ApplyToHeadersFooters( VirtualGroupContainer groupContainer, Action<HeaderFooterWrapper, bool> action )
    {
      foreach( HeaderFooterWrapper header in groupContainer.Headers )
      {
        action( header, true );
      }

      foreach( HeaderFooterWrapper footer in groupContainer.Footers )
      {
        action( footer, false );
      }
    }

    internal void PrepareElementInternal( VirtualGroupContainer groupContainer, DataPath dataPath, List<DataGridGroupConfiguration> configs )
    {
      groupContainer.Indent = this.GetInheritedSublevelIndent( configs );

      this.ApplyToHeadersFooters( groupContainer,
        ( h, isHeader ) =>
          this.PrepareGroupHeaderFooterInternalCore(
          h,
          ( isHeader ) ? LocationState.GroupHeader : LocationState.GroupFooter,
          dataPath ) );
    }

    internal void AttachElementInternal( VirtualGroupContainer groupContainer, DataPath dataPath )
    {
      this.AttachElementInternal( ( IDataGridElement )groupContainer, dataPath );

      this.ApplyToHeadersFooters( groupContainer, ( h, isHeader ) => this.AttachGroupHeaderFooterInternalCore( h, dataPath ) );

      this.ModifyGroupContextForElement( groupContainer, this.CreateGroupContext( dataPath ) );

      GroupDataItem groupDataItem = ( GroupDataItem )dataPath.LastChild;
      this.ModifyStatContextForElement( groupContainer, groupDataItem.StatContext );
    }

    internal void DetachElementInternal( VirtualGroupContainer groupContainer )
    {
      this.DetachElementInternal( ( IDataGridElement )groupContainer );
      this.ApplyToHeadersFooters( groupContainer, ( h, isHeader ) => this.DetachGroupHeaderFooterInternalCore( h ) );
    }

    internal void ClearElementInternal( VirtualGroupContainer groupContainer )
    {
      this.ApplyToHeadersFooters( groupContainer, ( h, isHeader ) => this.ClearGroupHeaderFooterInternalCore( h ) );
    }

    internal void PrepareGroupHeaderFooterInternalCore( HeaderFooterWrapper headerFooter, LocationState location, DataPath dataPath )
    {
      if( headerFooter.Child is GroupHeaderControl )
        this.PrepareGroupHeaderFooterInternal( ( GroupHeaderControl )headerFooter.Child, dataPath );
      else if( headerFooter.Child is Row )
        this.PrepareGroupHeaderFooterInternal( ( Row )headerFooter.Child, dataPath );
      else if( headerFooter.Child is FrameworkElement )
        this.PrepareGroupHeaderFooterInternal( ( FrameworkElement )headerFooter.Child, dataPath );
      else
        Debug.Assert( false );
    }

    internal void AttachGroupHeaderFooterInternalCore( HeaderFooterWrapper headerFooter, DataPath dataPath )
    {
      if( headerFooter.Child is GroupHeaderControl )
        this.AttachGroupHeaderFooterInternal( ( GroupHeaderControl )headerFooter.Child, dataPath );
      else if( headerFooter.Child is FrameworkElement )
        this.AttachGroupHeaderFooterInternal( ( FrameworkElement )headerFooter.Child, dataPath );
      else
        Debug.Assert( false );
    }

    internal void DetachGroupHeaderFooterInternalCore( HeaderFooterWrapper headerFooter )
    {
      if( headerFooter.Child is GroupHeaderControl )
        this.DetachGroupHeaderFooterInternal( ( GroupHeaderControl )headerFooter.Child );
      else if( headerFooter.Child is FrameworkElement )
        this.DetachGroupHeaderFooterInternal( ( FrameworkElement )headerFooter.Child );
      else
        Debug.Assert( false );
    }

    internal void ClearGroupHeaderFooterInternalCore( HeaderFooterWrapper headerFooter )
    {
      if( headerFooter.Child is GroupHeaderControl )
        this.ClearGroupHeaderFooterInternal( ( GroupHeaderControl )headerFooter.Child );
      else if( headerFooter.Child is FrameworkElement )
        this.ClearGroupHeaderFooterInternal( ( FrameworkElement )headerFooter.Child );
      else
        Debug.Assert( false );
    }

    internal void AttachGroupHeaderFooterInternal( FrameworkElement headerFooter, DataPath dataPath )
    {
      headerFooter.DataContext = dataPath.LastChild.Data;
    }




    internal void AttachGroupHeaderFooterInternal( GroupHeaderControl headerFooter, DataPath dataPath )
    {
      this.AttachGroupHeaderFooterInternal( ( FrameworkElement )headerFooter, dataPath );

      headerFooter.IsMouseOverGroupHeader = false;
      headerFooter.SetCommonState( GroupHeaderControl.CommonStateNormal, false );
      headerFooter.SetCurrentState( this.IsCurrentPath( dataPath ), false );
      headerFooter.SetExpansiontState( this.IsGroupExpanded( new DataGridPath( dataPath ) )
                                      ? GroupHeaderControl.ExpansionStateExpanded
                                      : GroupHeaderControl.ExpansionStateCollapsed
                                      , false );
      headerFooter.GroupPath = dataPath;

      List<DataGridGroupConfiguration> groupConfigurationsList = this.GetGroupConfigurationForPath( dataPath );
      headerFooter.GroupValueTemplate = this.GetGroupConfigurationGroupValueTemplate( groupConfigurationsList );
      Style groupStyle = this.GetGroupConfigurationGroupHeaderStyle( groupConfigurationsList );
      if( groupStyle != null )
      {
        headerFooter.Style = groupStyle;
      }
      else
      {
        headerFooter.ClearValue( GroupHeaderControl.StyleProperty );
      }

      this.UpdateGroupHeaderNavigationToolsVisibility( headerFooter, groupConfigurationsList, false );

      this.ModifyGroupContextForElement( headerFooter, this.CreateGroupContext( dataPath ) );

      GroupDataItem groupDataItem = ( GroupDataItem )dataPath.LastChild;
      this.ModifyStatContextForElement( headerFooter, groupDataItem.StatContext );
    }

    internal void DetachGroupHeaderFooterInternal( FrameworkElement headerFooter )
    {
      DependencyObject focusScope = this.RootHost;
      if( headerFooter.IsFocusAncestor( focusScope ) )
      {
        this.FocusDump.Focus();
      }
    }

    internal void DetachGroupHeaderFooterInternal( GroupHeaderControl headerFooter )
    {
      this.DetachGroupHeaderFooterInternal( ( FrameworkElement )headerFooter );
    }

    internal void PrepareGroupHeaderFooterInternal( FrameworkElement headerFooter, DataPath dataPath )
    {
    }

    internal void PrepareGroupHeaderFooterInternal( Row headerFooter, DataPath groupPath )
    {
      this.PrepareElementInternal( ( IColumnElementHost )headerFooter );
      headerFooter.CellsOffset = this.GetColumnElementsIdentForDepth( groupPath.Depth + 1 );
    }





    internal void PrepareGroupHeaderFooterInternal( GroupHeaderControl headerFooter, DataPath groupPath )
    {
      this.PrepareGroupHeaderFooterInternal( ( FrameworkElement )headerFooter, groupPath );

      if( groupPath.Depth > this.DataGridContext.GroupDescriptions.Count )
        throw new InvalidOperationException( "The temDataPath Depth must not be greater than the GroupDescriptions count." );

      headerFooter.CreateCommands();
    }

    internal void ClearGroupHeaderFooterInternal( FrameworkElement headerFooter )
    {
    }

    internal void ClearGroupHeaderFooterInternal( GroupHeaderControl headerFooter )
    {
      headerFooter.ReleaseCommands();
    }

    #endregion (VirtualGroupContainer)

    #region Cell Life Cycle

    internal ColumnElementWrapper CreateCellInternal( Row row, Column column )
    {
      return new ColumnElementWrapper( this.CreateCell( row, column ) );
    }

    protected virtual Cell CreateCell( Row row, Column column )
    {
      if( row is DataRow )
        return new DataCell();
      else if( row is ColumnManagerRow )
        return new ColumnManagerCell();

      return new Cell();
    }

    internal void PrepareElementInternal( Cell cell, Column column )
    {
    }

    internal void ClearElementInternal( Cell cell )
    {
    }

    internal void AttachElementInternalForCell( Cell cell, Column column, bool columnChanged )
    {
      cell.SetCellStyle( this.GetInheritedCellStyle( cell, column, cell.ParentRow ) );
      this.AttachElementInternal( cell, column, columnChanged );
      this.AttachElement( cell, column );
    }

    internal void AttachElementInternal( Cell cell, Column column, bool columnChanged )
    {
      this.UpdateSingleCellHorizontalContentAlignment( cell, column );
      this.UpdateSingleCellVerticalContentAlignment( cell, column );

      this.UpdateColumnElementCommonState( cell, cell.ParentColumnContainer.IsDragged, false, false, false );
    }

    internal void DetachElementInternal( Cell cell )
    {
    }

    protected virtual void PrepareElement( Cell cell, Column column )
    {

    }

    protected virtual void AttachElement( Cell cell, Column column )
    {

    }

    protected virtual void DetachElement( Cell cell )
    {

    }

    protected virtual void ClearElement( Cell cell )
    {

    }

    #endregion

    #region EditableCell Life Cycle

    private void AttachElementInternal( EditableCell cell, Column column, bool columnChanged )
    {
      this.AttachElementInternal( ( Cell )cell, column, columnChanged );

      cell.TextTrimming = this.GetInheritedTextTrimming( column );

      if( object.ReferenceEquals( this.CurrentCell, cell ) )
      {
        cell.SetCurrentState( true, false );
        this.EnsureCurrentIsFocused( cell );
      }
    }

    private void DetachElementInternal( EditableCell cell )
    {
      this.ResetElementInternal( cell );

      this.DetachElementInternal( ( Cell )cell );
    }

    private bool UpdateElementInternal( EditableCell cell, bool columnChanged )
    {
      EditableRow parentRow = cell.ParentRow as EditableRow;
      if( parentRow == null )
        return false;

      EditableCellViewModel viewModel = this.GetEditableCellViewModel( parentRow.ViewModel, cell.ParentColumn );
      bool updated = ( cell.ViewModel != viewModel );

      cell.ViewModel = viewModel;

      if( this.ShowEditor( cell ) )
      {
        if( this.SetEditor( cell, columnChanged ) )
        {
          updated = true;
        }
      }
      else
      {
        if( this.SetViewer( cell ) )
        {
          updated = true;
        }
      }

      return updated;
    }

    private void ResetElementInternal( EditableCell cell, bool releaseViewModel = true )
    {
      var viewModel = cell.ViewModel;
      if( viewModel == null )
        return;

      cell.ViewModel = null;

      if( releaseViewModel )
      {
        viewModel.Release();
      }
    }

    private EditableCellViewModel GetEditableCellViewModel( EditableRowViewModel rowViewModel, Column column )
    {
      if( ( rowViewModel == null ) || ( column == null ) )
        return null;

      return rowViewModel.GetCell( column );
    }

    private bool SetEditor( EditableCell cell, bool columnChanged )
    {
      Column column = cell.ParentColumn;
      Style editorStyle = this.GetInheritedCellEditorStyle( cell, column, cell.ParentRow );
      DataTemplate editorTemplate = this.GetInheritedCellEditorTemplate( cell );
      Type dataType = this.GetColumnDataType( column );

      bool dataTypeChanged = true;
      bool containerTypeChanged = true;
      bool templateChanged = true;

      EditableCellContentElement contentElement = cell.ContentElement;
      if( !columnChanged && ( contentElement != null ) )
      {
        dataTypeChanged = ( contentElement.DataType != dataType );
        containerTypeChanged = ( contentElement.ContainerType != EditableCellContentElementType.Editor );
        templateChanged = ( contentElement.Template != editorTemplate );
      }

      if( templateChanged && ( editorTemplate != null ) )
      {
        contentElement = new EditableCellContentElement( editorTemplate, EditableCellContentElementType.Editor, dataType );
      }
      else if( templateChanged || containerTypeChanged || dataTypeChanged )
      {
        FrameworkElement editor = this.GetEditorFromType( dataType );

        contentElement = new EditableCellContentElement( editor, EditableCellContentElementType.Editor, dataType );
      }

      if( ( editorTemplate == null ) || ( editorStyle != null ) )
      {
        FrameworkElement container = contentElement.Container;
        if( container.Style != editorStyle )
        {
          if( editorStyle != null )
          {
            container.Style = editorStyle;
          }
          else
          {
            container.ClearValue( FrameworkElement.StyleProperty );
          }
        }
      }

      bool newPresenter = !object.Equals( cell.ContentElement, contentElement );
      cell.ContentElement = contentElement;

      return newPresenter;
    }

    private bool SetViewer( EditableCell cell )
    {
      Column column = cell.ParentColumn;
      Style viewerStyle = this.GetInheritedCellViewerStyle( cell, column, cell.ParentRow );
      DataTemplate viewerTemplate = this.GetInheritedCellContentTemplate( cell );
      Type dataType = this.GetColumnDataType( column );

      EditableCellContentElement contentElement = cell.ContentElement;
      bool dataTypeChanged = ( contentElement == null ) || ( contentElement.DataType != dataType );
      bool containerTypeChanged = ( contentElement == null ) || ( contentElement.ContainerType != EditableCellContentElementType.Viewer );
      bool templateChanged = ( contentElement == null ) || ( contentElement.Template != viewerTemplate );

      if( templateChanged && ( viewerTemplate != null ) )
      {
        contentElement = new EditableCellContentElement( viewerTemplate, EditableCellContentElementType.Viewer, dataType );
      }
      else if( templateChanged || containerTypeChanged || dataTypeChanged )
      {
        FrameworkElement viewer = this.GetViewerFromType( dataType );

        contentElement = new EditableCellContentElement( viewer, EditableCellContentElementType.Viewer, dataType );
      }

      if( ( viewerTemplate == null ) || ( viewerStyle != null ) )
      {
        FrameworkElement container = contentElement.Container;
        if( container.Style != viewerStyle )
        {
          if( viewerStyle != null )
          {
            container.Style = viewerStyle;
          }
          else
          {
            container.ClearValue( FrameworkElement.StyleProperty );
          }
        }
      }

      bool newPresenter = !object.Equals( cell.ContentElement, contentElement );
      cell.ContentElement = contentElement;

      return newPresenter;
    }

    #endregion

    #region DataCell Life Cycle

    internal ColumnElementWrapper CreateDataCellInternal( DataRow row, Column column )
    {
      return new DataCellWrapper( (DataCell)this.CreateCell( row, column ) );
    }

    internal void PrepareElementInternal( DataCell cell, Column column )
    {
      this.PrepareElementInternal( ( Cell )cell, column );
      this.PrepareElement( ( Cell )cell, column );
    }

    internal void ClearElementInternal( DataCell cell )
    {
      this.ClearElementInternal( ( Cell )cell );
      this.ClearElement( ( Cell )cell );
    }

    internal void AttachElementInternal( DataCell cell, Column column, bool columnChanged )
    {
      this.AttachElementInternal( ( EditableCell )cell, column, columnChanged );

      DataRow parentRow = cell.ParentRow as DataRow;
      if( ( parentRow != null ) && ( parentRow.IsDisplayInitialized ) )
      {
        this.UpdateElementInternal( cell, columnChanged );
      }

      cell.SetCellStyle( this.GetInheritedCellStyle( cell, column, cell.ParentRow ) );
      this.AttachElement( ( Cell )cell, column );
    }

    internal void DetachElementInternal( DataCell cell )
    {
      this.DetachElementInternal( ( EditableCell )cell );
      this.DetachElement( ( Cell )cell );
    }

    internal void InitializeDisplay( DataCell cell )
    {
      this.UpdateCellCurrentState( cell, null, false );
      // RowCommonState is Set in m_datagrid.UpdateRowCommonState just up 
    }

    internal void UninitializeDisplay( DataCell cell )
    {
      // RowCommonState is Set in m_datagrid.UpdateRowCommonState just up 
    }

    #endregion

    #region ColumnManagerCell Life cycle

    internal ColumnElementWrapper CreateColumnManagerCellInternal( ColumnManagerRow row, Column column )
    {
      return new ColumnElementWrapper( this.CreateCell( row, column ) );
    }

    internal void PrepareElementInternal( ColumnManagerCell cell, Column column )
    {
      this.PrepareElementInternal( ( Cell )cell, column );

      cell.ToggleParentColumnIsFixedCommand = new DataGridCommand( new Action<object>( ( object param ) => this.OnToggleParentColumnIsFixedCommandExecuted( cell, param ) ),
                                                                   new Func<object, bool>( this.OnToggleParentColumnIsFixedCommandCanExecute ) );

      this.PrepareElement( ( Cell )cell, column );
    }

    internal void ClearElementInternal( ColumnManagerCell cell )
    {
      this.ClearElementInternal( ( Cell )cell );

      cell.ToggleParentColumnIsFixedCommand = null;

      this.ClearElement( ( Cell )cell );
    }

    internal void AttachElementInternal( ColumnManagerCell cell, Column column, bool columnChanged )
    {
      this.AttachElementInternal( ( Cell )cell, column, columnChanged );

      cell.Content = column.DisplayTitle;
      this.RefreshFixedColumnToggleButtonVisibility( cell, column, false );
      this.UpdateFixedColumnToggleButtonState( cell, column );
      this.UpdateColumnResizingThumbHitTestVisibility( cell );
      cell.ContentTemplate = this.GetInheritedCellContentTemplate( cell );
      cell.SetCellStyle( this.GetInheritedCellStyle( cell, column, cell.ParentRow ) );
      cell.SetSortingState( column.SortDirection, true );

      this.AttachElement( ( Cell )cell, column );
    }

    internal void DetachElementInternal( ColumnManagerCell cell )
    {
      this.DetachElementInternal( ( Cell )cell );

      this.DetachElement( ( Cell )cell );
    }

    #endregion














































    #region ColumnBackgroundControl Life Cycle

    internal ColumnBackgroundControlWrapper CreateColumnBackgroundControlInternal( Column column )
    {
      return new ColumnBackgroundControlWrapper( this.CreateColumnBackgroundControl( column ) );
    }

    protected virtual ColumnBackgroundControl CreateColumnBackgroundControl( Column column )
    {
      return new ColumnBackgroundControl();
    }

    internal void PrepareElementInternal( ColumnBackgroundControlWrapper ColumnBackgroundControlWrapper, Column column )
    {
      ColumnBackgroundControlWrapper.NotifyIsLastLevelUpdated();
    }

    internal void ClearElementInternal( ColumnBackgroundControl ColumnBackgroundControl )
    {
    }

    internal void AttachElementInternal( ColumnBackgroundControl ColumnBackgroundControl, Column column, bool columnChanged )
    {
      ColumnBackgroundControl.SetColumnVisibilityStates( this.IsColumnFixed( column ) ?
                                                       ColumnBackgroundControl.ColumnVisibilityStateFixed :
                                                       ColumnBackgroundControl.ColumnVisibilityStateScrolling,
                                                       true );
    }

    internal void DetachElementInternal( ColumnBackgroundControl ColumnBackgroundControl )
    {
    }

    #endregion











    #region FixedHeaders Life cycle

    internal void PrepareHeaderFooterCore( UIElement element, LocationState locationState )
    {
        if( element is ColumnManagerRow )
          this.PrepareHeaderFooter( ( ColumnManagerRow )element );
        else if( element is NotificationControl )
          this.PrepareHeaderFooter( ( NotificationControl )element );
        else if( element is Row )
          this.PrepareHeaderFooter( ( Row )element );
        else
          Debug.Assert( false, "Unexpected element type" );
    }

    internal void PrepareHeaderFooter( ColumnManagerRow row )
    {
      row.SetRowStyle( this.ColumnManagerRowStyle );
      this.PrepareHeaderFooter( ( Row )row );
    }





    internal void PrepareHeaderFooter( Row row )
    {
      row.CellsOffset = this.GetColumnElementsIdentForDepth( 1 );
      this.PrepareElementInternal( ( IColumnElementHost )row );
    }







    internal void PrepareHeaderFooter( NotificationControl notificationControl )
    {
      notificationControl.UpdateVisibilityStates( false );
      notificationControl.CreateCommands();
      this.NotificationReceived += notificationControl.OnNotificationReceived;
    }

    internal void ClearHeaderFooterCore( UIElement element )
    {
      if( element is IColumnElementHost )
        this.ClearElementInternal( ( IColumnElementHost )element );
      else if( element is NotificationControl )
        this.ClearElementInternal( ( NotificationControl )element );
    }




    internal void ClearElementInternal( NotificationControl notificationControl )
    {
      notificationControl.ReleaseCommands();
      this.NotificationReceived -= notificationControl.OnNotificationReceived;
    }

    #endregion

    internal GroupContext CreateGroupContext( DataPath groupPath )
    {
      GroupDataItem groupDataItem = ( GroupDataItem )groupPath.LastChild;
      GroupDescription groupDescription = this.DataGridContext.GroupDescriptions[ groupPath.Depth - 1 ];
      Nullable<long> itemsCount = ( groupDataItem.ItemCount != null ) ? groupDataItem.ItemCount.Value : ( Nullable<long> )null;
      IEnumerable<object> items = null;

      if( groupDataItem.Items != null )
      {
        items = groupDataItem.Items.Select( ( path ) => this.DataGridContext.DataSource.DataHelper.ConvertBack( path ) ).ToList();
      }

      return
        new GroupContext(
          ( groupPath.Depth == this.DataGridContext.GroupDescriptions.Count ),
          groupDescription,
          new DataGridPath( groupPath ),
          itemsCount,
          items );
    }

    internal void ReleaseElement( ILayoutElement element )
    {
      RecycleElementVisitor visitor = new RecycleElementVisitor( this );
      ( ( IVisitorElement )element ).Accept( visitor );
    }

    internal void OnColumnAdded( object sender, XceedRoutedEventArgs args )
    {
    }

    internal void OnColumnRemoved( object sender, XceedRoutedEventArgs args )
    {
      ColumnContainer columnContainer = ( ( ColumnContainerEventArgs )args ).ColumnContainer;

      this.DataGridPanel.ColumnPanel.RecycleElement( columnContainer );
    }

    private ColumnElementWrapper GetColumnElement( IColumnElementHost host, ColumnContainer column )
    {
      foreach( ColumnElementWrapper colElement in host.Elements )
      {
        if( column.Contains( colElement ) )
          return colElement;
      }

      return null;
    }





    private IEnumerable<FilterOperator> GetSupportedFilterOperators( Cell cell )
    {
      var dataGridContext = this.DataGridContext;
      var dataSource = ( dataGridContext != null )
        ? dataGridContext.DataSource
        : null;

      var parentColumn = cell.ParentColumn;

      Type dataType = null;
      if( ( parentColumn != null ) && ( dataSource != null ) )
      {
        dataSource.Provider.TryGetElementMemberType( parentColumn.DisplayMemberBindingInfo.Path.Path, out dataType );  
      }

      if( dataType == null || dataSource == null )
        return new FilterOperator[ 0 ];

      return dataSource.GetSupportedFilterOperators( dataType );
    }

    private Column GetRelatedColumn( GroupDescription groupDescription )
    {
      // Find the column that has the same GroupDescription.
      PropertyGroupDescription propertyGroupDescription = groupDescription as PropertyGroupDescription;
      if( propertyGroupDescription != null )
      {
        return this.DataGridContext.Columns[ propertyGroupDescription.PropertyName ];
      }

      return null;
    }

    private void UpdateSingleCellVerticalContentAlignment( Cell cell, Column column )
    {
      cell.SetVerticalContentAlignmentProperty( column.CellVerticalContentAlignment );
    }

    private void UpdateCellHorizontalContentAlignment( Column column )
    {
      // Update each child of the column
      ColumnContainer columnContainer = this.GetColumnContainer( column );
      if( columnContainer != null )
      {
        foreach( Cell cell in columnContainer.Cells )
        {
          this.UpdateSingleCellHorizontalContentAlignment( cell, column );
        }
      }
    }

    private void UpdateSingleCellHorizontalContentAlignment( Cell cell, Column column )
    {
      var horizontalContentAlignment = column.CellHorizontalContentAlignment;
      var columnDataType = this.Columns.Contains( column ) ? this.GetColumnDataType( column ) : null;

      if( ( !horizontalContentAlignment.HasValue )
         && ( this.AutoAlignColumns )
         && ( columnDataType != null ) )
      {
        // Else, if DataGridControl.AutoAlignColumns is true, get the alignment
        // based on the data type of the column.
        horizontalContentAlignment = DataGridControl.GetHorizontalAlignmentForDataType( columnDataType );
      }

      cell.SetHorizontalContentAlignmentProperty( horizontalContentAlignment );
    }

    private void OnToggleParentColumnIsFixedCommandExecuted( ColumnManagerCell cell, object parameter )
    {
      Column parentColumn = cell.ParentColumn;
      if( parentColumn != null )
      {
        this.UpdateFixedColumnsCount( parentColumn );
      }
    }

    private bool OnToggleParentColumnIsFixedCommandCanExecute( object parameter )
    {
      return this.AllowFixedColumns;
    }

    private void UpdateFixedColumnsCount( Column column )
    {
      Debug.Assert( column != null );

      IEnumerable<Column> orderedColumns = this.GetOrderedColumns();
      IEnumerable<Column> leftFixedColumns = this.GetFixedColumns( orderedColumns, true );
      IEnumerable<Column> rightFixedColumns = this.GetFixedColumns( orderedColumns, false );

      if( leftFixedColumns.Contains( column ) )
      {
        this.IncrementLeftFixedColumnCount( false );
      }
      else if( rightFixedColumns.Contains( column ) )
      {
        this.IncrementRightFixedColumnCount( false );
      }
      else
      {
        List<Column> scrollableColumns = this.GetScrollableColumns().ToList();

        if( object.ReferenceEquals( scrollableColumns.First(), column ) )
        {
          this.IncrementLeftFixedColumnCount( true );
        }
        else if( object.ReferenceEquals( scrollableColumns.Last(), column ) )
        {
          this.IncrementRightFixedColumnCount( true );
        }
      }
    }

    private void IncrementLeftFixedColumnCount( bool increment )
    {
      this.LeftFixedColumnCount += increment ? 1 : -1;
    }

    private void IncrementRightFixedColumnCount( bool increment )
    {
      this.RightFixedColumnCount += increment ? 1 : -1;
    }

    private FrameworkElement GetViewerFromType( Type dataType )
    {
      Type underlyingType = this.GetUnderlyingType( dataType );

      if( ( typeof( string ) == underlyingType )
        || ( typeof( int ) == underlyingType )
        || ( typeof( long ) == underlyingType )
        || ( typeof( short ) == underlyingType )
        || ( typeof( byte ) == underlyingType )
        || ( typeof( uint ) == underlyingType )
        || ( typeof( ulong ) == underlyingType )
        || ( typeof( ushort ) == underlyingType )
        || ( typeof( sbyte ) == underlyingType )
        || ( typeof( double ) == underlyingType )
        || ( typeof( float ) == underlyingType )
        || ( typeof( decimal ) == underlyingType )
        || ( typeof( DateTime ) == underlyingType )
        || ( typeof( TimeSpan ) == underlyingType )
        || ( typeof( Guid ) == underlyingType )
        || ( underlyingType.IsEnum ) )
        return new TextBlock();

      if( typeof( bool ) == underlyingType )
        return new DataGridCheckBox()
        {
          IsEnabled = false,
          IsThreeState = ( underlyingType != dataType )
        };

      return new ContentControl();
    }

    private FrameworkElement GetEditorFromType( Type dataType )
    {
      Type underlyingType = this.GetUnderlyingType( dataType );

      if( typeof( string ) == underlyingType )
        return this.CreateDataGridTextBox();

      if( typeof( DateTime ) == underlyingType )
        return new DatePicker();

      if( typeof( bool ) == underlyingType )
        return new DataGridCheckBox()
        {
          IsThreeState = ( underlyingType != dataType )
        };

      if( ( typeof( int ) == underlyingType )
        || ( typeof( long ) == underlyingType )
        || ( typeof( short ) == underlyingType )
        || ( typeof( byte ) == underlyingType )
        || ( typeof( uint ) == underlyingType )
        || ( typeof( ulong ) == underlyingType )
        || ( typeof( ushort ) == underlyingType )
        || ( typeof( sbyte ) == underlyingType ) )
        return this.CreateDataGridNumericTextBox( dataType, NumberStyles.Integer );

      if( ( typeof( double ) == underlyingType )
        || ( typeof( float ) == underlyingType )
        || ( typeof( decimal ) == underlyingType ) )
        return this.CreateDataGridNumericTextBox( dataType, NumberStyles.Number );

      if( underlyingType.IsEnum && ( underlyingType.GetCustomAttributes( typeof( FlagsAttribute ), true ).Length == 0 ) )
        return this.CreateEnumEditor( dataType );

      return new ContentControl();
    }

    private Type GetUnderlyingType( Type dataType )
    {
      if( dataType.IsNullableStruct() )
        return Nullable.GetUnderlyingType( dataType );

      return dataType;
    }

    private DataGridTextBox CreateDataGridTextBox()
    {
      var control = new DataGridTextBox();

      ISupportInitialize initializer = control;
      initializer.BeginInit();
      initializer.EndInit();

      return control;
    }

    private DataGridNumericTextBox CreateDataGridNumericTextBox( Type dataType, NumberStyles numberStyle )
    {
      var control = new DataGridNumericTextBox();

      ISupportInitialize initializer = control;
      initializer.BeginInit();

      try
      {
        control.DataType = dataType;
        control.NumberStyles = numberStyle;
      }
      finally
      {
        initializer.EndInit();
      }

      return control;
    }

    private FrameworkElement CreateEnumEditor( Type dataType )
    {
      IList items = ( IList )Activator.CreateInstance( typeof( ObservableCollection<> ).MakeGenericType( dataType ) );
      Type enumType = this.GetUnderlyingType( dataType );
      bool isNullable = ( enumType != dataType );

      Debug.Assert( enumType.IsEnum );

      if( isNullable )
      {
        items.Add( null );
      }

      foreach( var value in enumType.GetFields( BindingFlags.Public | BindingFlags.Static ) )
      {
        items.Add( value.GetValue( null ) );
      }

      return new DataGridComboBox()
      {
        ItemsSource = items
      };
    }

    private static HorizontalAlignment? GetHorizontalAlignmentForDataType( Type columnDataType )
    {
      var type = ( columnDataType.IsNullableStruct() )
        ? Nullable.GetUnderlyingType( columnDataType )
        : columnDataType;

      // Special case for enum, we will align to the left since
      // the default viewer for a enum will show a string corresponding
      // to the enum item itself.
      if( type.IsEnum )
        return HorizontalAlignment.Left;

      // Otherwise, we will align based on the TypeCode.
      var typeCode = Type.GetTypeCode( type );
      switch( typeCode )
      {
        case TypeCode.Byte:
        case TypeCode.DateTime:
        case TypeCode.Decimal:
        case TypeCode.Double:
        case TypeCode.Int16:
        case TypeCode.Int32:
        case TypeCode.Int64:
        case TypeCode.SByte:
        case TypeCode.Single:
        case TypeCode.UInt16:
        case TypeCode.UInt32:
        case TypeCode.UInt64:
          return HorizontalAlignment.Right;

        case TypeCode.Boolean:
          return HorizontalAlignment.Center;

        case TypeCode.Char:
        case TypeCode.String:
          return HorizontalAlignment.Left;

        default:
          return null;
      }
    }
  }
}
